package com.ssau.expert.core.impl;

import com.ssau.expert.core.api.DatabaseCoreAPI;
import com.ssau.expert.core.model.Condition;
import com.ssau.expert.core.model.Operation;
import com.ssau.expert.core.model.Rule;
import com.ssau.expert.core.model.Variable;
import com.ssau.expert.core.util.DataTypeHelper;
import com.ssau.expert.core.util.StringHelper;
import com.ssau.expert.exception.IllegalOperationException;
import com.ssau.expert.exception.RuleParseException;
import com.ssau.expert.exception.SemanticException;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Author: Sergey Saiyan
 * Created: 16.09.13 10:46
 */
public class RuleParser {

    protected static VariableTable variableTable = VariableTable.get();

    protected static DatabaseCoreAPI databaseCoreAPI = DatabaseImpl.get();

    @SuppressWarnings("unchecked")
    public static List<String> uploadRules(File file) throws RuleParseException, SemanticException, IOException {
        Collection<Rule> rules = new ArrayList<>(databaseCoreAPI.getRuleCollection());

        List<String> lines = FileUtils.readLines(file);

        try {
            databaseCoreAPI.clearRules();
            for (String line : lines) {
                if ("".equals(line)) continue;
                databaseCoreAPI.putRule(createRule(line));
            }
        } catch (Exception ex) {
            databaseCoreAPI.restoreRules(rules);
            throw ex;
        }
        return databaseCoreAPI.getRules();
    }

    private static Rule createRule(String line) throws RuleParseException, SemanticException {
        Rule rule = new Rule();
        String copyLine = line.trim().toUpperCase();
        rule.setRuleString(copyLine);
        if (!copyLine.contains("IF")) {
            throw new RuleParseException("Rule '%s' doesn't contain IF statement", line);
        } else if (!copyLine.contains("THEN")) {
            throw new RuleParseException("Rule '%s' doesn't contain THEN statement", line);
        } else if (StringHelper.countOf(copyLine, "IF") > 1) {
            throw new RuleParseException("Rule '%s' contains more then one IF statement", line);
        } else if (StringHelper.countOf(copyLine, "THEN") > 1) {
            throw new RuleParseException("Rule '%s' contains more then one THEN statement", line);
        } else if (copyLine.indexOf("THEN") + 5 > copyLine.length()) {
            throw new RuleParseException("Rule '%s' has empty THEN statement", line);
        }

        /* IF A>3 THEN I:=1*/
        String id = copyLine.substring(0, copyLine.indexOf(':'));
        rule.setId(id);

        String ifStatement = copyLine.substring(copyLine.indexOf("IF") + 3, copyLine.indexOf("THEN"));
        String thenStatement = copyLine.substring(copyLine.indexOf("THEN") + 5);

        String conditions[] = ifStatement.trim().split("&&");
        for (String condition_ : conditions) {
            /* e.g. condition_ = ' a > 3 '*/
            String[] elements = condition_.trim().split(" ");
            if (elements.length != 3) {
                throw new RuleParseException("Condition '%s' in rule '%s' is invalid. Maybe missing spaces.", ifStatement, copyLine);
            }
            String variable = elements[0];
            String operation_ = elements[1];
            String value = elements[2];

            Condition condition = new Condition();
            condition.setConditionString(condition_);
            Variable var;
            if (variableTable.contains(variable)) {
                var = variableTable.getVariable(variable);
            } else {
                var = new Variable();
                var.setName(variable);
                variableTable.putVariable(var);
            }
            condition.setVariable(var);
            try {
                Operation operation = Operation.fromId(operation_);
                if (Operation.ASSIGN != operation) {
                    condition.setOperation(operation);
                } else {
                    throw new SemanticException("Detected assign operation '%s' in a condition statement '%s' of a rule '%s'",
                            operation_, condition_.trim());
                }
            } catch (IllegalOperationException ex) {
                throw new RuleParseException("Invalid operation '%s' in a condition '%s' of a row '%s'",
                        operation_, condition_.trim(), copyLine);
            }

            Class valueClass = DataTypeHelper.getClassByValue(value);
            if (var.getClazz() == null) {
                var.setClazz(valueClass);
            } else {
                if (!valueClass.equals(var.getClazz()))
                    throw new SemanticException("Variable '%s' has '%s' data type, but was assigned value '%s' " +
                            "of '%s' data type in a rule '%s'",
                            var.getName(), var.getClazz().getName(), value, valueClass, copyLine);
            }
            condition.setConditionValue(value);

            rule.getConditions().add(condition);
        }

        String[] thenElements = thenStatement.trim().split(" ");
        if (thenElements.length != 3) {
            throw new RuleParseException("THEN statement '%s' in a rule '%s' is invalid", thenStatement, copyLine);
        }
        rule.setRuleOutput(thenStatement.trim());
        String variable = thenElements[0];
        Variable var;
        if (variableTable.contains(variable)) {
            var = variableTable.getVariable(variable);
        } else {
            var = new Variable();
            var.setName(variable);
            variableTable.putVariable(var);
        }
        rule.setOutputVariable(var);

        return rule;
    }
}
